#!/bin/bash

# 通用 HTTPS 自动配置脚本
# 适用于任何需要反向代理的服务
# 使用 Let's Encrypt 免费证书，自动申请和续期

VERSION="1.0.0"

# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[0;33m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
NC='\033[0m' # No Color

# 日志函数
log_info() {
    echo -e "${BLUE}[INFO]${NC} $1"
}

log_success() {
    echo -e "${GREEN}[SUCCESS]${NC} $1"
}

log_error() {
    echo -e "${RED}[ERROR]${NC} $1"
}

log_warning() {
    echo -e "${YELLOW}[WARNING]${NC} $1"
}

log_step() {
    echo -e "${CYAN}[STEP]${NC} $1"
}

# 检查是否以 root 权限运行
check_root() {
    if [ "$EUID" -ne 0 ]; then
        log_error "请使用 root 权限运行此脚本"
        exit 1
    fi
}

# 显示横幅
show_banner() {
    clear
    echo "=============================================================="
    echo "           通用 HTTPS 自动配置脚本 v${VERSION}"
    echo "           Let's Encrypt 免费 SSL 证书"
    echo "=============================================================="
    echo
}

# 显示帮助信息
show_help() {
    cat << EOF
用法: $0 [选项]

选项:
    -h, --help              显示此帮助信息
    -d, --domain DOMAIN     指定域名
    -e, --email EMAIL       指定邮箱
    -p, --port PORT         指定后端服务端口
    -n, --name NAME         指定服务名称（用于配置文件命名）
    --websocket             启用 WebSocket 支持（默认启用）
    --no-websocket          禁用 WebSocket 支持
    --cert-only             仅申请证书，不配置 Nginx
    --renew                 续期现有证书
    --list                  列出所有已配置的证书
    --remove DOMAIN         删除指定域名的证书和配置

示例:
    # 交互式配置
    $0

    # 命令行参数配置
    $0 -d api.example.com -e admin@example.com -p 3000 -n myapi

    # 仅申请证书
    $0 -d example.com -e admin@example.com --cert-only

    # 续期证书
    $0 --renew

    # 列出所有证书
    $0 --list

    # 删除配置
    $0 --remove api.example.com

EOF
}

# 检测操作系统
detect_os() {
    if [ -f /etc/os-release ]; then
        . /etc/os-release
        OS=$ID
        VERSION=$VERSION_ID
    else
        log_error "无法检测操作系统"
        exit 1
    fi
    log_info "检测到操作系统: $OS $VERSION"
}

# 安装 Certbot
install_certbot() {
    log_step "检查 Certbot 安装状态..."

    if command -v certbot >/dev/null 2>&1; then
        log_success "Certbot 已安装"
        return 0
    fi

    log_info "开始安装 Certbot..."

    case $OS in
        ubuntu|debian)
            apt update
            apt install -y certbot python3-certbot-nginx
            ;;
        centos|rhel|rocky|almalinux)
            if [ "${VERSION%%.*}" -ge 8 ]; then
                dnf install -y certbot python3-certbot-nginx
            else
                yum install -y certbot python3-certbot-nginx
            fi
            ;;
        fedora)
            dnf install -y certbot python3-certbot-nginx
            ;;
        *)
            log_error "不支持的操作系统: $OS"
            exit 1
            ;;
    esac

    if command -v certbot >/dev/null 2>&1; then
        log_success "Certbot 安装成功"
    else
        log_error "Certbot 安装失败"
        exit 1
    fi
}

# 检查 Nginx 是否安装
check_nginx() {
    if ! command -v nginx >/dev/null 2>&1; then
        log_warning "未检测到 Nginx"
        read -p "是否安装 Nginx？(Y/n): " install_nginx
        if [[ ! $install_nginx =~ ^[Nn]$ ]]; then
            install_nginx_package
        else
            log_error "Nginx 是必需的，退出"
            exit 1
        fi
    else
        log_success "Nginx 已安装"
    fi
}

# 安装 Nginx
install_nginx_package() {
    log_step "安装 Nginx..."

    case $OS in
        ubuntu|debian)
            apt update
            apt install -y nginx
            ;;
        centos|rhel|rocky|almalinux|fedora)
            if [ "${VERSION%%.*}" -ge 8 ]; then
                dnf install -y nginx
            else
                yum install -y nginx
            fi
            ;;
    esac

    systemctl enable nginx
    systemctl start nginx
    log_success "Nginx 安装完成"
}

# 检查域名解析
check_domain_dns() {
    local domain=$1
    log_step "检查域名 DNS 解析..."

    # 获取服务器公网 IP
    local server_ip=$(curl -s --max-time 5 ifconfig.me 2>/dev/null || curl -s --max-time 5 icanhazip.com 2>/dev/null || curl -s --max-time 5 ipinfo.io/ip 2>/dev/null)

    if [ -z "$server_ip" ]; then
        log_warning "无法获取服务器公网 IP，跳过 DNS 检查"
        return 0
    fi

    log_info "服务器公网 IP: $server_ip"

    # 解析域名
    local domain_ip=$(dig +short $domain 2>/dev/null | grep -E '^[0-9.]+$' | tail -n1)

    if [ -z "$domain_ip" ]; then
        # 尝试使用 nslookup
        domain_ip=$(nslookup $domain 2>/dev/null | grep -A1 "Name:" | grep "Address:" | awk '{print $2}' | tail -n1)
    fi

    if [ -z "$domain_ip" ]; then
        log_error "域名 $domain 无法解析"
        log_error "请确保域名已正确解析到服务器 IP: $server_ip"
        read -p "是否继续？(y/N): " continue_anyway
        if [[ ! $continue_anyway =~ ^[Yy]$ ]]; then
            exit 1
        fi
    elif [ "$domain_ip" != "$server_ip" ]; then
        log_warning "域名解析 IP ($domain_ip) 与服务器 IP ($server_ip) 不匹配"
        read -p "是否继续？(y/N): " continue_anyway
        if [[ ! $continue_anyway =~ ^[Yy]$ ]]; then
            exit 1
        fi
    else
        log_success "域名解析正确: $domain -> $server_ip"
    fi
}

# 创建 certbot 验证目录
create_certbot_dir() {
    log_step "创建 Let's Encrypt 验证目录..."
    mkdir -p /var/www/certbot
    chmod 755 /var/www/certbot
    log_success "验证目录创建完成"
}

# 配置防火墙
configure_firewall() {
    log_step "配置防火墙规则..."

    if command -v ufw >/dev/null 2>&1 && ufw status | grep -q "Status: active"; then
        # Ubuntu/Debian 使用 ufw
        ufw allow 80/tcp >/dev/null 2>&1
        ufw allow 443/tcp >/dev/null 2>&1
        log_success "UFW 防火墙规则已配置"
    elif command -v firewall-cmd >/dev/null 2>&1 && systemctl is-active --quiet firewalld; then
        # CentOS/RHEL 使用 firewalld
        firewall-cmd --permanent --add-service=http >/dev/null 2>&1
        firewall-cmd --permanent --add-service=https >/dev/null 2>&1
        firewall-cmd --reload >/dev/null 2>&1
        log_success "Firewalld 防火墙规则已配置"
    else
        log_warning "未检测到活动的防火墙，请手动确保 80 和 443 端口已开放"
    fi
}

# 申请 SSL 证书
obtain_certificate() {
    local domain=$1
    local email=$2

    log_step "开始申请 SSL 证书..."
    log_info "域名: $domain"
    log_info "邮箱: $email"

    # 检查证书是否已存在
    if [ -d "/etc/letsencrypt/live/$domain" ]; then
        log_warning "证书已存在: $domain"
        read -p "是否强制重新申请？(y/N): " force_renew
        if [[ $force_renew =~ ^[Yy]$ ]]; then
            certbot certonly \
                --nginx \
                --non-interactive \
                --agree-tos \
                --email "$email" \
                -d "$domain" \
                --force-renewal
        else
            log_info "使用现有证书"
            return 0
        fi
    else
        # 使用 certbot 申请证书
        certbot certonly \
            --nginx \
            --non-interactive \
            --agree-tos \
            --email "$email" \
            -d "$domain"
    fi

    if [ $? -eq 0 ]; then
        log_success "SSL 证书申请成功！"
        log_info "证书位置: /etc/letsencrypt/live/$domain/"
        return 0
    else
        log_error "SSL 证书申请失败"
        log_error "可能的原因："
        log_error "  1. 域名未正确解析到此服务器"
        log_error "  2. 80 端口未开放或被占用"
        log_error "  3. 防火墙或安全组阻止了访问"
        return 1
    fi
}

# 配置自动续期
setup_auto_renewal() {
    log_step "配置证书自动续期..."

    # 测试续期命令
    certbot renew --dry-run >/dev/null 2>&1
    if [ $? -eq 0 ]; then
        log_success "证书自动续期配置成功"
        log_info "Certbot 会自动通过 systemd timer 续期证书"
    else
        log_warning "证书续期测试失败，但已配置自动续期"
    fi

    # 确保 certbot timer 已启用
    if command -v systemctl >/dev/null 2>&1; then
        systemctl enable certbot.timer >/dev/null 2>&1
        systemctl start certbot.timer >/dev/null 2>&1
        log_info "Certbot 自动续期服务已启用"
    fi

    # 创建续期后重载 nginx 的 hook
    mkdir -p /etc/letsencrypt/renewal-hooks/deploy
    if [ ! -f /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh ]; then
        cat > /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh << 'EOF'
#!/bin/bash
systemctl reload nginx
EOF
        chmod +x /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh
        log_success "Nginx 自动重载 hook 已配置"
    fi
}

# 生成 Nginx 配置
generate_nginx_config() {
    local domain=$1
    local backend_port=$2
    local service_name=$3
    local enable_websocket=$4

    local config_content=""

    # HTTP 服务器配置
    config_content+="# HTTP 服务器 - 用于 Let's Encrypt 验证和重定向到 HTTPS
server {
    listen 80;
    server_name ${domain};

    # Let's Encrypt ACME 验证路径
    location /.well-known/acme-challenge/ {
        root /var/www/certbot;
    }

    # 其他所有请求重定向到 HTTPS
    location / {
        return 301 https://\$server_name\$request_uri;
    }
}

"

    # HTTPS 服务器配置
    config_content+="# HTTPS 服务器 - ${service_name}
server {
    listen 443 ssl http2;
    server_name ${domain};

    # SSL 证书配置
    ssl_certificate /etc/letsencrypt/live/${domain}/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/${domain}/privkey.pem;

    # SSL 安全配置
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384';
    ssl_prefer_server_ciphers off;

    # SSL 会话缓存
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 10m;
    ssl_session_tickets off;

    # OCSP Stapling
    ssl_stapling on;
    ssl_stapling_verify on;
    ssl_trusted_certificate /etc/letsencrypt/live/${domain}/chain.pem;
    resolver 8.8.8.8 8.8.4.4 valid=300s;
    resolver_timeout 5s;

    # 安全头部
    add_header Strict-Transport-Security \"max-age=63072000; includeSubDomains; preload\" always;
    add_header X-Frame-Options \"SAMEORIGIN\" always;
    add_header X-Content-Type-Options \"nosniff\" always;
    add_header X-XSS-Protection \"1; mode=block\" always;

    # 访问日志
    access_log /var/log/nginx/${service_name}-ssl-access.log;
    error_log /var/log/nginx/${service_name}-ssl-error.log;

    # 反向代理配置
    location / {
        proxy_pass http://127.0.0.1:${backend_port};
        proxy_http_version 1.1;
"

    # WebSocket 支持
    if [ "$enable_websocket" = "yes" ]; then
        config_content+="
        # WebSocket 支持
        proxy_set_header Upgrade \$http_upgrade;
        proxy_set_header Connection \"upgrade\";
"
    fi

    config_content+="
        # 传递真实客户端信息
        proxy_set_header Host \$host;
        proxy_set_header X-Real-IP \$remote_addr;
        proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto \$scheme;

        # 超时设置
        proxy_connect_timeout 60s;
        proxy_send_timeout 60s;
        proxy_read_timeout 60s;

        # 缓冲设置
        proxy_buffering off;
    }

    # 客户端上传大小限制
    client_max_body_size 50M;

    # Gzip 压缩
    gzip on;
    gzip_vary on;
    gzip_min_length 1024;
    gzip_types text/plain text/css text/xml text/javascript application/json application/javascript application/xml+rss application/x-javascript;
}
"

    echo "$config_content"
}

# 创建 Nginx 配置文件
create_nginx_config() {
    local domain=$1
    local backend_port=$2
    local service_name=$3
    local enable_websocket=$4

    log_step "创建 Nginx 配置文件..."

    # 确定配置文件路径
    local config_file=""
    if [ -d "/etc/nginx/sites-available" ]; then
        # Ubuntu/Debian 风格
        config_file="/etc/nginx/sites-available/${service_name}"
        local enabled_file="/etc/nginx/sites-enabled/${service_name}"
    else
        # CentOS/RHEL 风格
        config_file="/etc/nginx/conf.d/${service_name}.conf"
    fi

    # 生成配置内容
    local config_content=$(generate_nginx_config "$domain" "$backend_port" "$service_name" "$enable_websocket")

    # 写入配置文件
    echo "$config_content" > "$config_file"

    # 创建软链接（如果是 Ubuntu/Debian）
    if [ -d "/etc/nginx/sites-available" ]; then
        ln -sf "$config_file" "$enabled_file" 2>/dev/null
    fi

    log_success "Nginx 配置文件已创建: $config_file"

    # 测试 Nginx 配置
    nginx -t >/dev/null 2>&1
    if [ $? -eq 0 ]; then
        log_success "Nginx 配置测试通过"
        systemctl reload nginx
        log_success "Nginx 已重新加载"
        return 0
    else
        log_error "Nginx 配置测试失败"
        nginx -t
        return 1
    fi
}

# 列出所有证书
list_certificates() {
    log_step "已配置的 SSL 证书："
    echo
    certbot certificates
}

# 续期证书
renew_certificates() {
    log_step "开始续期证书..."
    certbot renew
    if [ $? -eq 0 ]; then
        log_success "证书续期成功"
        systemctl reload nginx
    else
        log_error "证书续期失败"
    fi
}

# 删除证书和配置
remove_configuration() {
    local domain=$1

    log_warning "即将删除域名 $domain 的证书和 Nginx 配置"
    read -p "确认删除？(y/N): " confirm
    if [[ ! $confirm =~ ^[Yy]$ ]]; then
        log_info "已取消"
        return 0
    fi

    # 删除证书
    log_step "删除 SSL 证书..."
    certbot delete --cert-name "$domain" --non-interactive

    # 删除 Nginx 配置
    log_step "删除 Nginx 配置..."

    # 查找并删除配置文件
    local config_files=$(find /etc/nginx -name "*${domain}*" -o -name "*$(echo $domain | sed 's/\./-/g')*" 2>/dev/null)

    if [ -n "$config_files" ]; then
        echo "$config_files" | while read file; do
            rm -f "$file"
            log_info "已删除: $file"
        done
    fi

    # 重载 Nginx
    nginx -t && systemctl reload nginx

    log_success "删除完成"
}

# 显示完成信息
show_completion_info() {
    local domain=$1
    local backend_port=$2
    local service_name=$3

    echo
    log_success "=============================================================="
    log_success "           HTTPS 配置完成！"
    log_success "=============================================================="
    echo
    log_info "服务信息:"
    log_info "  服务名称: $service_name"
    log_info "  访问地址: https://$domain"
    log_info "  后端端口: $backend_port"
    echo
    log_info "证书信息:"
    log_info "  证书路径: /etc/letsencrypt/live/$domain/fullchain.pem"
    log_info "  私钥路径: /etc/letsencrypt/live/$domain/privkey.pem"
    log_info "  证书有效期: 90 天"
    log_info "  自动续期: 已启用（每天自动检查）"
    echo
    log_info "管理命令:"
    log_info "  查看证书: certbot certificates"
    log_info "  手动续期: certbot renew"
    log_info "  测试续期: certbot renew --dry-run"
    log_info "  查看配置: cat /etc/nginx/sites-available/$service_name"
    echo
    log_info "Nginx 管理:"
    log_info "  重启: systemctl restart nginx"
    log_info "  重载: systemctl reload nginx"
    log_info "  状态: systemctl status nginx"
    log_info "  测试: nginx -t"
    echo
    log_success "现在可以通过 HTTPS 安全访问你的服务了！"
    echo
}

# 交互式配置
interactive_setup() {
    show_banner

    echo "请输入配置信息："
    echo

    # 域名
    while true; do
        read -p "域名（例如: api.example.com）: " DOMAIN
        if [ -z "$DOMAIN" ]; then
            log_error "域名不能为空"
        elif [[ ! "$DOMAIN" =~ ^[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$ ]]; then
            log_error "域名格式不正确"
        else
            break
        fi
    done

    # 邮箱
    while true; do
        read -p "邮箱（用于证书通知）: " EMAIL
        if [ -z "$EMAIL" ]; then
            log_error "邮箱不能为空"
        elif [[ ! "$EMAIL" =~ ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ ]]; then
            log_error "邮箱格式不正确"
        else
            break
        fi
    done

    # 后端端口
    while true; do
        read -p "后端服务端口（1-65535）: " BACKEND_PORT
        if [[ ! "$BACKEND_PORT" =~ ^[0-9]+$ ]] || [ "$BACKEND_PORT" -lt 1 ] || [ "$BACKEND_PORT" -gt 65535 ]; then
            log_error "端口号无效，请输入 1-65535 之间的数字"
        else
            break
        fi
    done

    # 服务名称
    read -p "服务名称（用于配置文件命名，默认: ${DOMAIN%%.*}）: " SERVICE_NAME
    SERVICE_NAME=${SERVICE_NAME:-${DOMAIN%%.*}}
    # 清理服务名称，只保留字母数字和连字符
    SERVICE_NAME=$(echo "$SERVICE_NAME" | sed 's/[^a-zA-Z0-9-]/-/g')

    # WebSocket 支持
    read -p "是否启用 WebSocket 支持？(Y/n): " enable_ws
    if [[ $enable_ws =~ ^[Nn]$ ]]; then
        ENABLE_WEBSOCKET="no"
    else
        ENABLE_WEBSOCKET="yes"
    fi

    # 仅申请证书
    read -p "是否仅申请证书（不配置 Nginx）？(y/N): " cert_only
    if [[ $cert_only =~ ^[Yy]$ ]]; then
        CERT_ONLY="yes"
    else
        CERT_ONLY="no"
    fi

    echo
    log_info "配置信息:"
    log_info "  域名: $DOMAIN"
    log_info "  邮箱: $EMAIL"
    log_info "  后端端口: $BACKEND_PORT"
    log_info "  服务名称: $SERVICE_NAME"
    log_info "  WebSocket: $ENABLE_WEBSOCKET"
    log_info "  仅申请证书: $CERT_ONLY"
    echo
    read -p "确认以上信息无误？(Y/n): " CONFIRM
    if [[ $CONFIRM =~ ^[Nn]$ ]]; then
        log_info "已取消"
        exit 0
    fi
}

# 主函数
main() {
    # 解析命令行参数
    DOMAIN=""
    EMAIL=""
    BACKEND_PORT=""
    SERVICE_NAME=""
    ENABLE_WEBSOCKET="yes"
    CERT_ONLY="no"
    ACTION="setup"

    while [[ $# -gt 0 ]]; do
        case $1 in
            -h|--help)
                show_help
                exit 0
                ;;
            -d|--domain)
                DOMAIN="$2"
                shift 2
                ;;
            -e|--email)
                EMAIL="$2"
                shift 2
                ;;
            -p|--port)
                BACKEND_PORT="$2"
                shift 2
                ;;
            -n|--name)
                SERVICE_NAME="$2"
                shift 2
                ;;
            --websocket)
                ENABLE_WEBSOCKET="yes"
                shift
                ;;
            --no-websocket)
                ENABLE_WEBSOCKET="no"
                shift
                ;;
            --cert-only)
                CERT_ONLY="yes"
                shift
                ;;
            --renew)
                ACTION="renew"
                shift
                ;;
            --list)
                ACTION="list"
                shift
                ;;
            --remove)
                ACTION="remove"
                DOMAIN="$2"
                shift 2
                ;;
            *)
                log_error "未知选项: $1"
                show_help
                exit 1
                ;;
        esac
    done

    check_root
    detect_os

    # 执行对应操作
    case $ACTION in
        list)
            install_certbot
            list_certificates
            exit 0
            ;;
        renew)
            install_certbot
            renew_certificates
            exit 0
            ;;
        remove)
            if [ -z "$DOMAIN" ]; then
                log_error "请指定要删除的域名"
                exit 1
            fi
            remove_configuration "$DOMAIN"
            exit 0
            ;;
        setup)
            # 继续执行配置流程
            ;;
    esac

    # 如果没有提供参数，使用交互式配置
    if [ -z "$DOMAIN" ] || [ -z "$EMAIL" ]; then
        interactive_setup
    else
        # 使用命令行参数
        if [ -z "$SERVICE_NAME" ]; then
            SERVICE_NAME=${DOMAIN%%.*}
            SERVICE_NAME=$(echo "$SERVICE_NAME" | sed 's/[^a-zA-Z0-9-]/-/g')
        fi

        if [ "$CERT_ONLY" = "no" ] && [ -z "$BACKEND_PORT" ]; then
            log_error "请指定后端服务端口（-p 或 --port）"
            exit 1
        fi
    fi

    # 执行配置步骤
    echo
    check_nginx
    check_domain_dns "$DOMAIN"
    install_certbot
    create_certbot_dir
    configure_firewall

    # 申请证书
    if obtain_certificate "$DOMAIN" "$EMAIL"; then
        setup_auto_renewal

        if [ "$CERT_ONLY" = "no" ]; then
            create_nginx_config "$DOMAIN" "$BACKEND_PORT" "$SERVICE_NAME" "$ENABLE_WEBSOCKET"
            show_completion_info "$DOMAIN" "$BACKEND_PORT" "$SERVICE_NAME"
        else
            log_success "证书申请完成！"
            log_info "证书位置: /etc/letsencrypt/live/$DOMAIN/"
        fi
    else
        log_error "SSL 证书配置失败"
        exit 1
    fi
}

# 运行主函数
main "$@"
